/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/models/db/common_table.h>

using namespace journal::model::db;

common_table::common_table(std::weak_ptr<icommon_table_client> client, std::shared_ptr<s::iengine> dbEngine, std::wstring name) :
    _client(client),
    _dbEngine(dbEngine),
    _name(name)
{
}

std::shared_ptr<icommon_table> common_table::create(std::weak_ptr<icommon_table_client> client, std::shared_ptr<s::iengine> dbEngine, std::wstring name)
{
    return std::make_shared<common_table>(client, dbEngine, name);
}

void common_table::initialize()
{
    if (_table != nullptr)
    {
        return;
    }

    // Create an empty table...
    _table = s::createTable(_name);

    // Add column definitions...
    _client.lock()->defineColumns(_table);

    // Add index...
    _index = s::createIndex(L"PK_Index", true, true);
    _table->addIndex(_index);

    // Add index columns...
    _client.lock()->defineIndexColumns(_index);

    // create the table in the database engine..
    _dbEngine->openTable(_table);

    // Those have to happen AFTER the the createTable statement so that cells
    // get assigned the correct column ids.
    // Create cells for insertion..

    // Bug 322: TRACK: can we get rid of this 'cell' concept?
    _row = std::make_shared<std::vector<std::shared_ptr<s::icell>>>();
    _client.lock()->defineRowCells(_row);
}

std::wstring common_table::name()
{
    return _name;
}

std::shared_ptr<s::iengine> common_table::dbEngine()
{
    return _dbEngine;
}

std::shared_ptr<s::itable> common_table::tableDefinition()
{
    return _table;
}

std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> common_table::stagingRow()
{
    return _row;
}

std::shared_ptr<s::iindex> common_table::primaryIndex()
{
    return _index;
}

unsigned int common_table::getNextId()
{
    baja::uint32 maxId = 0;

    std::shared_ptr<std::vector<std::shared_ptr<s::iindex_column>>> indexColumns = _index->getIndexColumns();

    BAJA_ASSERT(indexColumns->size() > 0, L"Empty index is not allowed!");

    // We assume the last column to be the unique column...
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(indexColumns->at(indexColumns->size() - 1)->getColumn());

    this->readAll(result);

    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    baja::uint32 readId;

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        // There should be one cell per row only...
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(*cellIt);
            readId = typedCell->getValue();
            if (maxId < readId)
            {
                maxId = readId;
            }
        }
    }

    if (rows->size() != 0)
    {
        ++maxId;
    }

    return maxId;
}

void common_table::insert(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> stagingRow)
{
    _dbEngine->insert(_table, stagingRow);
}

void common_table::update(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> stagingRow, 
                          baja::uint32 criteriaCount)
{
    // since this is the primary index, if all the index columns are used, the result set 
    // must contain only a single record.
    bool unique = _index->getIndexColumns()->size() == criteriaCount;
    _dbEngine->update(_table, _index, stagingRow, criteriaCount, unique);
}

void common_table::read(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> stagingRow, 
                        baja::uint32 criteriaCount,
                        std::shared_ptr<s::iresult> result)
{
    bool assumeSingleMatch = stagingRow->size() == criteriaCount;
    _dbEngine->read(_table, _index, stagingRow, criteriaCount, assumeSingleMatch, result);
}

void common_table::read(std::shared_ptr<s::iindex> keyIndex,
                        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> stagingRow, 
                        baja::uint32 criteriaCount,
                        bool assumeSingleMatch,
                        std::shared_ptr<s::iresult> result)
{
    _dbEngine->read(_table, keyIndex, stagingRow, criteriaCount, assumeSingleMatch, result);
}

void common_table::readAll(std::shared_ptr<s::iresult> result)
{
    _dbEngine->readAll(_table, _index, result);
}

void common_table::readIds(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> criteriaRow,
                           std::shared_ptr<s::icolumn> idColumn,
                           std::vector<b::uint32>& ids)
{
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(idColumn);

    if (criteriaRow == nullptr)
    {
        readAll(result);
    }
    else
    {
        _dbEngine->read(_table, _index, criteriaRow, 1, false, result);
    }

    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        // There should be one cell per row only...
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
            ids.push_back(typedCell->getValue());
        }
    }
}

void common_table::readActiveIds(
    std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> criteriaRow,
    std::shared_ptr<s::icolumn> idColumn,
    std::shared_ptr<s::icolumn> activeColumn,
    std::vector<b::uint32>& ids)
{
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(idColumn);
    result->addColumn(activeColumn);

    if (criteriaRow == nullptr)
    {
        readAll(result);
    }
    else
    {
        _dbEngine->read(_table, _index, criteriaRow, 1, false, result);
    }

    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;

        b::uint32 id = 0;
        b::uint32 columnIndex = 0;  // we use columnIndex to make sure we get the active flag after we get the id value.

        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);

            if( cell->getColumnId() == idColumn->getId() )
            {
                BAJA_ASSERT(columnIndex == 0, L"incorrect order of columns");

                id = typedCell->getValue();
            }
            else if( cell->getColumnId() == activeColumn->getId() )
            {
                BAJA_ASSERT(columnIndex == 1, L"incorrect order of columns");

                b::uint32 active = typedCell->getValue();
                if (active != 0)
                {
                    ids.push_back(id);
                }
            }
            ++columnIndex;
        }
    }
}

void common_table::dispose(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> criteriaRow)
{
    _dbEngine->dispose(
        _table,
        _index,
        criteriaRow,
        static_cast<b::uint32>(_index->getIndexColumns()->size()),
        false);
}

void common_table::disposeInactiveRecords(
    std::shared_ptr<s::iindex> keyIndex,
    std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> criteriaRow, 
    b::uint32 criteriaCount,
    bool assumeSingleMatch)
{
    _dbEngine->dispose(
        _table,
        keyIndex,
        criteriaRow,
        criteriaCount,
        assumeSingleMatch);
}

void common_table::disposeAll()
{
    _dbEngine->disposeAll(_table, _index);
}

std::shared_ptr<icommon_table> journal::model::db::createCommonTable(std::weak_ptr<icommon_table_client> client, std::shared_ptr<s::iengine> dbEngine, std::wstring name)
{
    return common_table::create(client, dbEngine, name);
}


